package com.wiscamp.ninechapters.problems.application.services;

import com.wiscamp.ninechapters.problems.application.assemblers.ExamAssembler;
import com.wiscamp.ninechapters.problems.application.assemblers.ProblemTypeAssembler;
import com.wiscamp.ninechapters.problems.application.assemblers.QuestionTypeAssembler;
import com.wiscamp.ninechapters.problems.application.dto.ExamProblemTypesDto;
import com.wiscamp.ninechapters.problems.application.dto.commands.exams.*;
import com.wiscamp.ninechapters.problems.application.dto.vo.exams.ExamTypeRelationVO;
import com.wiscamp.ninechapters.problems.application.dto.vo.exams.ExamVO;
import com.wiscamp.ninechapters.problems.application.dto.vo.exams.ScoringRuleVO;
import com.wiscamp.ninechapters.problems.domain.models.exams.ExamTypeRelation;
import com.wiscamp.ninechapters.problems.domain.repositories.ExamRepository;
import com.wiscamp.ninechapters.problems.domain.repositories.ProblemTypeRepository;
import com.wiscamp.ninechapters.problems.domain.repositories.QuestionTypeRepository;
import com.wiscamp.ninechapters.problems.domain.services.ExamService;
import com.wiscamp.ninechapters.problems.enums.ProblemModes;
import cube.common.core.CollectionUtils;
import cube.ddd.application.BaseAppService;
import cube.ddd.application.command.DeleteCommand;
import cube.ddd.application.command.DisableCommand;
import cube.ddd.application.command.EnableCommand;
import cube.ddd.aspect.HandleServiceException;
import cube.ddd.common.query.PagedObjects;
import cube.ddd.common.query.PagedQuery;
import cube.ddd.utils.AppServiceUtils;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import org.springframework.stereotype.Service;

import java.util.Collection;

@Service
public class ExamAppService extends BaseAppService {

    private final ProblemTypeRepository problemTypeRepository;

    private final QuestionTypeRepository questionTypeRepository;

    private final ExamRepository examRepository;

    private final ExamService examService;


    public ExamAppService(ProblemTypeRepository problemTypeRepository, QuestionTypeRepository questionTypeRepository, ExamRepository examRepository, ExamService examService) {
        this.problemTypeRepository = problemTypeRepository;
        this.questionTypeRepository = questionTypeRepository;
        this.examRepository = examRepository;
        this.examService = examService;
    }

    //region Exam
    @HandleServiceException
    public ExamVO getExam(@Min(0) long examId) {
        return AppServiceUtils.getById(examId, examRepository::getExam,
                ExamAssembler.Instance::toExamVO);
    }

    @HandleServiceException
    public ExamVO getExamByCode(@NotBlank String examCode, @Min(0) long organizationId) {
        return AppServiceUtils.getByTextAndId(examCode, organizationId, examRepository::getExamByCode,
                ExamAssembler.Instance::toExamVO);
    }

    @HandleServiceException
    public Collection<ExamVO> getExamsByName(@NotBlank String examName, @Min(0) long organizationId) {
        return AppServiceUtils.getListByTextAndId(examName, organizationId, examRepository::getExamsByName,
                ExamAssembler.Instance::toExamVO);
    }

    @HandleServiceException
    public Collection<ExamVO> getExamsByOrganization(@Min(0) long organizationId) {
        return AppServiceUtils.getListById(organizationId, examRepository::getExamsByOrganization,
                ExamAssembler.Instance::toExamVO);
    }

    @HandleServiceException
    public PagedObjects<ExamVO> getExamsByQuery(@NotNull PagedQuery query) {
        var pagedObjects = examRepository.getExamsByQuery(query);
        return AppServiceUtils.convertPagedObjects(pagedObjects, ExamAssembler.Instance::toExamVO);
    }

    @HandleServiceException
    public ExamVO createExam(@NotNull CreateExamCommand command) {
        command.setOperatorId(getCurrentUserId());
        return AppServiceUtils.createOrUpdateBy(command, command.getOperatorId(), examService::createExam,
                ExamAssembler.Instance::toExamVO, ExamAssembler.Instance::fromCreateCommand);
    }

    @HandleServiceException
    public ExamVO updateExam(@NotNull UpdateExamCommand command) {
        command.setOperatorId(getCurrentUserId());
        return AppServiceUtils.createOrUpdateBy(command, command.getOperatorId(), examService::updateExam,
                ExamAssembler.Instance::toExamVO, ExamAssembler.Instance::fromUpdateCommand);
    }

    @HandleServiceException
    public boolean deleteExam(@NotNull DeleteCommand command) {
        command.setOperatorId(getCurrentUserId());
        return AppServiceUtils.delete(command.getId(), command.getOperatorId(), examService::deleteExam);
    }

    @HandleServiceException
    public boolean enableExam(@NotNull EnableCommand command) {
        command.setOperatorId(getCurrentUserId());
        return AppServiceUtils.enable(command.getId(), command.getOperatorId(), examService::enableExam);
    }

    @HandleServiceException
    public boolean disableExam(@NotNull DisableCommand command) {
        command.setOperatorId(getCurrentUserId());
        return AppServiceUtils.disable(command.getId(), command.getOperatorId(), examService::disableExam);
    }
    //endregion


    //region typeRelation
    @HandleServiceException
    public ExamTypeRelationVO getExamTypeRelation(@Min(0) long id) {
        return AppServiceUtils.getById(id, examRepository::getExamTypeRelation,
                ExamAssembler.Instance::toExamTypeRelationVO);
    }

    @HandleServiceException
    public Collection<ExamTypeRelationVO> getExamTypeRelationsByExam(@Min(0) long examId) {
        return AppServiceUtils.getListById(examId, examRepository::getTypeRelationsByExam,
                ExamAssembler.Instance::toExamTypeRelationVO);
    }

    /*@HandleServiceException
    public CombineExamTypeRelationsDto getCombineExamTypeRelations(@Min(0) long examId) {
        *//*var examTypeRelations = getExamTypeRelations(examId);
        CombineExamTypeRelationsDto dto = new CombineExamTypeRelationsDto();
        dto.setExamId(examId);
        for (var examTypeRelation : examTypeRelations) {
            var typeRelation = examTypeRelation.getTypeRelation();
            if (Objects.isNull(typeRelation)) continue;
            var problemType = typeRelation.getProblemType();
            if (Objects.isNull(problemType)) continue;
            var problemTypeVO = ProblemTypeAssembler.Instance.toProblemTypeVO(problemType);
            // 先清空TypeRelation中的与QuestionType的绑定，再将ExamTypeRelation中与QuestionType的绑定加入
            problemType.setQuestionTypes(new ArrayList<>());
            var questionType = typeRelation.getQuestionType();

            if (Objects.isNull(questionType)) continue;
            var questionTypeVO = QuestionTypeAssembler.Instance.toQuestionTypeVO(questionType);

            var problemMode = problemType.getProblemMode();
            switch (problemMode) {
                case SIMPLE:
                    addToProblemTypes(dto.getSimpleProblemTypes(), problemTypeVO, questionTypeVO);
                    break;
                case COMPOSITE:
                    addToProblemTypes(dto.getCompositeProblemTypes(), problemTypeVO, questionTypeVO);
                    break;
                case HYBRID:
                    addToProblemTypes(dto.getHybridProblemTypes(), problemTypeVO, questionTypeVO);
                    break;
            }
        }
        return dto;*//*
        return null;
    }*/

    /*private void addToProblemTypes(Collection<ProblemTypeVO> problemTypes, ProblemTypeVO problemType, QuestionTypeVO questionType) {
        boolean foundProblemType = false;
        for (var type : problemTypes) {
            if (problemType.getTypeId() == type.getTypeId()) {
                type.getQuestionTypes().add(questionType);
                return;
            }
        }
        if (!foundProblemType) {
            problemType.getQuestionTypes().add(questionType);
            problemTypes.add(problemType);
        }
    }*/

    @HandleServiceException
    public ExamTypeRelationVO createExamTypeRelation(@NotNull CreateExamTypeRelationCommand command) {
        return AppServiceUtils.createOrUpdateBy(command, command.getOperatorId(), examService::createExamTypeRelation,
                ExamAssembler.Instance::toExamTypeRelationVO, ExamAssembler.Instance::fromCreateCommand);
    }

    @HandleServiceException
    public ExamTypeRelationVO updateExamTypeRelation(@NotNull UpdateExamTypeRelationCommand command) {
        return AppServiceUtils.createOrUpdateBy(command, command.getOperatorId(), examService::updateExamTypeRelation,
                ExamAssembler.Instance::toExamTypeRelationVO, ExamAssembler.Instance::fromUpdateCommand);
    }

    @HandleServiceException
    public boolean deleteExamTypeRelation(DeleteCommand command) {
        return AppServiceUtils.delete(command.getId(), command.getOperatorId(), examService::deleteExamTypeRelation);
    }

    @HandleServiceException
    public ExamProblemTypesDto getProblemTypesByExam(long examId) {
        var typeRelations = examRepository.getTypeRelationsByExam(examId);
        var typeIds = typeRelations.stream().map(ExamTypeRelation::getProblemTypeId).toList();
        var types = problemTypeRepository.getProblemTypesByIds(typeIds);
        var typeVOs = CollectionUtils.toArrayList(types, ProblemTypeAssembler.Instance::toProblemTypeVO);

        for (var typeVO : typeVOs) {
            var questionTypeIds = typeRelations.stream()
                    .filter(r -> r.getProblemTypeId() == typeVO.getTypeId())
                    .map(ExamTypeRelation::getQuestionTypeId).toList();
            var questionTypes = questionTypeRepository.getQuestionTypesByIds(questionTypeIds);
            var questionTypeVOs = CollectionUtils.toArrayList(questionTypes, QuestionTypeAssembler.Instance::toQuestionTypeVO);
            typeVO.setQuestionTypes(questionTypeVOs);
        }

        var simpleTypes = typeVOs.stream().filter(type -> type.getProblemModeId() == ProblemModes.SIMPLE.getValue()).toList();
        var compositeTypes = typeVOs.stream().filter(type -> type.getProblemModeId() == ProblemModes.COMPOSITE.getValue()).toList();
        var hybridTypes = typeVOs.stream().filter(type -> type.getProblemModeId() == ProblemModes.HYBRID.getValue()).toList();

        var dto = new ExamProblemTypesDto();
        dto.setSimpleProblemTypes(simpleTypes);
        dto.setCompositeProblemTypes(compositeTypes);
        dto.setHybridProblemTypes(hybridTypes);
        return dto;
    }
    //endregion

    //region ScoringRule
    @HandleServiceException
    public ScoringRuleVO getScoringRule(@Min(0) long id) {
        return AppServiceUtils.getById(id, examRepository::getScoringRule,
                ExamAssembler.Instance::toScoringRuleVO);
    }

    @HandleServiceException
    public Collection<ScoringRuleVO> getScoringRulesByExam(@Min(0) long examId) {
        return AppServiceUtils.getListById(examId, examRepository::getScoringRulesByExam,
                ExamAssembler.Instance::toScoringRuleVO);
    }

    @HandleServiceException
    public ScoringRuleVO createScoringRule(@NotNull CreateScoringRuleCommand command) {
        return AppServiceUtils.createOrUpdateBy(command, command.getOperatorId(), examService::createScoringRule,
                ExamAssembler.Instance::toScoringRuleVO, ExamAssembler.Instance::fromCreateCommand);
    }

    @HandleServiceException
    public ScoringRuleVO updateScoringRule(@NotNull UpdateScoringRuleCommand command) {
        return AppServiceUtils.createOrUpdateBy(command, command.getOperatorId(), examService::updateScoringRule,
                ExamAssembler.Instance::toScoringRuleVO, ExamAssembler.Instance::fromUpdateCommand);
    }

    @HandleServiceException
    public boolean deleteScoringRule(DeleteCommand command) {
        return AppServiceUtils.delete(command.getId(), command.getOperatorId(), examService::deleteScoringRule);
    }
    //endregion
}
